#region License
/* **********************************************************************************
 * Copyright (c) Roman Ivantsov
 * This source code is subject to terms and conditions of the MIT License
 * for Irony. A copy of the license can be found in the License.txt file
 * at the root of this distribution.
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
 * MIT License.
 * You must not remove this notice from this software.
 * **********************************************************************************/
//with contributions by Andrew Bradnan and Alexey Yakovlev
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Configuration;
using System.Text.RegularExpressions;
using System.Xml;
using Irony.Parsing;
using Irony.GrammarExplorer.Properties;
using Irony.GrammarExplorer.Highlighter;
using NMF.Languages.AnyText;
using NMF.Languages.AnyText.Transformations;
using NMF.Languages.AnyText.ClassConstruction;

namespace Irony.GrammarExplorer
{
    public partial class fmGrammarExplorer : Form
    {
        public fmGrammarExplorer()
        {
            InitializeComponent();

            CreateAnyTextParser();
        }

        //fields
        Grammar _grammar;
        LanguageData _language;
        Parser _parser;
        ParsingContext _context;
        ParseTree _parseTree;
        bool _loaded;
        bool _treeClickDisabled; //to temporarily disable tree click when we locate the node programmatically

        Parser _anytextParser;
        LanguageData _anytextData;
        Grammar _anytextGrammar;
        AnyTextTransformation _anytextTransformation = new AnyTextTransformation();
        ClassBuilder _classBuilder = new ClassBuilder();
        Initializer _initializer = new Initializer();

        #region Form load/unload events
        private void fmExploreGrammar_Load(object sender, EventArgs e)
        {
            ClearLanguageInfo();
            try
            {
                txtSource.Text = Settings.Default.SourceSample;
                chkParserTrace.Checked = Settings.Default.EnableTrace;
                chkDisableHili.Checked = Settings.Default.DisableHili;
            }
            catch { }
            _loaded = true;
        }

        private void fmExploreGrammar_FormClosing(object sender, FormClosingEventArgs e)
        {
            Settings.Default.SourceSample = txtSource.Text;
            Settings.Default.EnableTrace = chkParserTrace.Checked;
            Settings.Default.DisableHili = chkDisableHili.Checked;
            Settings.Default.Save();
        }//method
        #endregion

        #region Show... methods
        //Show... methods ######################################################################################################################
        private void ClearLanguageInfo()
        {
            lblLanguage.Text = string.Empty;
            lblLanguageVersion.Text = string.Empty;
            lblLanguageDescr.Text = string.Empty;
            txtGrammarComments.Text = string.Empty;
        }

        private void ClearParserOutput()
        {
            lblSrcLineCount.Text = string.Empty;
            lblSrcTokenCount.Text = "";
            lblParseTime.Text = "";
            lblParseErrorCount.Text = "";

            lstTokens.Items.Clear();
            gridCompileErrors.Rows.Clear();
            gridParserTrace.Rows.Clear();
            lstTokens.Items.Clear();
            tvParseTree.Nodes.Clear();
            Application.DoEvents();
        }

        private void ShowLanguageInfo()
        {
            if (_grammar == null) return;
            var langAttr = LanguageAttribute.GetValue(_grammar.GetType());
            if (langAttr == null) return;
            lblLanguage.Text = langAttr.LanguageName;
            lblLanguageVersion.Text = langAttr.Version;
            lblLanguageDescr.Text = langAttr.Description;
            txtGrammarComments.Text = _grammar.GrammarComments;
        }

        private void ShowCompilerErrors(ParseTree parseTree)
        {
            gridCompileErrors.Rows.Clear();
            if (parseTree == null || parseTree.ParserMessages.Count == 0) return;
            foreach (var err in parseTree.ParserMessages)
                gridCompileErrors.Rows.Add(err.Location, err, err.ParserState);
            var needPageSwitch = tabBottom.SelectedTab != pageParserOutput &&
              !(tabBottom.SelectedTab == pageParserTrace && chkParserTrace.Checked);
            if (needPageSwitch)
                tabBottom.SelectedTab = pageParserOutput;
        }

        private void ShowParseTrace()
        {
            gridParserTrace.Rows.Clear();
            foreach (var entry in _context.ParserTrace)
            {
                int index = gridParserTrace.Rows.Add(entry.State, entry.StackTop, entry.Input, entry.Message);
                if (entry.IsError)
                    gridParserTrace.Rows[gridParserTrace.Rows.Count - 1].DefaultCellStyle.ForeColor = Color.Red;
            }
            //Show tokens
            foreach (Token tkn in _parseTree.Tokens)
            {
                if (chkExcludeComments.Checked && tkn.Category == TokenCategory.Comment) continue;
                lstTokens.Items.Add(tkn);
            }
        }//method

        private void ShowCompileStats(ParseTree parseTree)
        {
            if (parseTree == null) return;
            lblSrcLineCount.Text = string.Empty;
            if (parseTree.Tokens.Count > 0)
                lblSrcLineCount.Text = (parseTree.Tokens[parseTree.Tokens.Count - 1].Location.Line + 1).ToString();
            lblSrcTokenCount.Text = parseTree.Tokens.Count.ToString();
            lblParseTime.Text = parseTree.ParseTimeMilliseconds.ToString();
            lblParseErrorCount.Text = parseTree.ParserMessages.Count.ToString();
            Application.DoEvents();
            //Note: this time is "pure" parse time; actual delay after cliking "Compile" includes time to fill ParseTree, AstTree controls
        }

        private void ShowParseTree()
        {
            tvParseTree.Nodes.Clear();
            if (_parseTree == null) return;
            AddParseNodeRec(null, _parseTree.Root);
        }
        private void AddParseNodeRec(TreeNode parent, ParseTreeNode node)
        {
            if (node == null) return;
            string txt = node.ToString();
            TreeNode tvNode = (parent == null ? tvParseTree.Nodes.Add(txt) : parent.Nodes.Add(txt));
            tvNode.Tag = node;
            foreach (var child in node.ChildNodes)
                AddParseNodeRec(tvNode, child);
        }

        private void ShowParserConstructionResults()
        {
            lblParserStateCount.Text = _language.ParserData.States.Count.ToString();
            lblParserConstrTime.Text = _language.ConstructionTime.ToString();
            txtParserStates.Text = string.Empty;
            gridGrammarErrors.Rows.Clear();
            txtTerms.Text = string.Empty;
            txtNonTerms.Text = string.Empty;
            txtParserStates.Text = string.Empty;
            tabBottom.SelectedTab = pageLanguage;
            if (_parser == null) return;
            txtTerms.Text = ParserDataPrinter.PrintTerminals(_language);
            txtNonTerms.Text = ParserDataPrinter.PrintNonTerminals(_language);
            txtParserStates.Text = ParserDataPrinter.PrintStateList(_language);
            ShowGrammarErrors();
        }//method

        private void ShowGrammarErrors()
        {
            gridGrammarErrors.Rows.Clear();
            var errors = _parser.Language.Errors;
            if (errors.Count == 0) return;
            foreach (var err in errors)
                gridGrammarErrors.Rows.Add(err.Level.ToString(), err.Message, err.State);
            if (tabBottom.SelectedTab != pageGrammarErrors)
                tabBottom.SelectedTab = pageGrammarErrors;
        }

        private void ShowSourcePosition(int position, int length)
        {
            if (position < 0) return;
            txtSource.SelectionStart = position;
            txtSource.SelectionLength = length;
            //txtSource.Select(location.Position, length);
            txtSource.DoCaretVisible();
            if (tabGrammar.SelectedTab != pageTest)
                tabGrammar.SelectedTab = pageTest;
            txtSource.Focus();
            //lblLoc.Text = location.ToString();
        }
        private void ShowSourcePositionAndTraceToken(int position, int length)
        {
            ShowSourcePosition(position, length);
            //find token in trace
            for (int i = 0; i < lstTokens.Items.Count; i++)
            {
                var tkn = lstTokens.Items[i] as Token;
                if (tkn.Location.Position == position)
                {
                    lstTokens.SelectedIndex = i;
                    return;
                }//if
            }//for i
        }
        private void LocateParserState(ParserState state)
        {
            if (state == null) return;
            if (tabGrammar.SelectedTab != pageParserStates)
                tabGrammar.SelectedTab = pageParserStates;
            //first scroll to the bottom, so that scrolling to needed position brings it to top
            txtParserStates.SelectionStart = txtParserStates.Text.Length - 1;
            txtParserStates.ScrollToCaret();
            DoSearch(txtParserStates, "State " + state.Name, 0);
        }

        private void SelectTreeNode(TreeView tree, TreeNode node)
        {
            _treeClickDisabled = true;
            tree.SelectedNode = node;
            if (node != null)
                node.EnsureVisible();
            _treeClickDisabled = false;
        }



        private void ClearRuntimeInfo()
        {
            lnkShowErrLocation.Enabled = false;
            lnkShowErrStack.Enabled = false;
            txtOutput.Text = string.Empty;
        }

        #endregion

        #region Parsing and running

        private void CreateParser()
        {
            StopHighlighter();
            btnRun.Enabled = false;
            txtOutput.Text = string.Empty;
            _parseTree = null;

            btnRun.Enabled = _grammar is ICanRunSample;
            _language = new LanguageData(_grammar);
            _parser = new Parser(_language);
            ShowParserConstructionResults();
            StartHighlighter();
        }

        private void CreateAnyTextParser()
        {
            _anytextGrammar = AnyTextInternalGrammar.Instance;
            _anytextTransformation = new AnyTextTransformation();
            StopAnyTextHighlighter();
            _anytextData = new LanguageData(_anytextGrammar);
            _anytextParser = new Parser(_anytextData);
            StartAnyTextHighlighter();
        }

        private void ParseSample()
        {
            ClearParserOutput();
            if (_parser == null || !_parser.Language.CanParse()) return;
            _parseTree = null;
            GC.Collect(); //to avoid disruption of perf times with occasional collections
            _parser.TracingEnabled = chkParserTrace.Checked;
            try
            {
                _context = _parser.Parse(txtSource.Text, "<source>");
            }
            catch (Exception ex)
            {
                gridCompileErrors.Rows.Add(null, ex.Message, null);
                tabBottom.SelectedTab = pageParserOutput;
                throw;
            }
            finally
            {
                _parseTree = _context.CurrentParseTree;
                ShowCompilerErrors(_parseTree);
                if (chkParserTrace.Checked)
                {
                    ShowParseTrace();
                }
                ShowCompileStats(_parseTree);
                ShowParseTree();
            }

            if (!_parseTree.HasErrors())
            {
                var obj = _initializer.CreateObject(_parseTree);
                Console.WriteLine("Juhu");
            }
        }

        private void RunSample()
        {
            ClearRuntimeInfo();
            Stopwatch sw = new Stopwatch();
            int oldGcCount;
            txtOutput.Text = "";
            try
            {
                if (_parseTree == null)
                    ParseSample();
                if (_parseTree.ParserMessages.Count > 0) return;

                GC.Collect(); //to avoid disruption of perf times with occasional collections
                oldGcCount = GC.CollectionCount(0);
                System.Threading.Thread.Sleep(100);

                sw.Start();
                var iRunner = _grammar as ICanRunSample;
                var args = new RunSampleArgs(_language, txtSource.Text, _parseTree);
                string output = iRunner.RunSample(args);
                sw.Stop();
                lblRunTime.Text = sw.ElapsedMilliseconds.ToString();
                var gcCount = GC.CollectionCount(0) - oldGcCount;
                lblGCCount.Text = gcCount.ToString();
                WriteOutput(output);
                tabBottom.SelectedTab = pageOutput;
            }
            finally
            {
                sw.Stop();
            }//finally
        }//method

        private void WriteOutput(string text)
        {
            if (string.IsNullOrEmpty(text)) return;
            txtOutput.Text += text + Environment.NewLine;
            txtOutput.Select(txtOutput.Text.Length - 1, 0);
        }

        #endregion

        #region miscellaneous: LoadSourceFile, Search, Source highlighting
        private void LoadSourceFile(string path)
        {
            _parseTree = null;
            StreamReader reader = null;
            try
            {
                reader = new StreamReader(path);
                txtSource.Text = null;  //to clear any old formatting
                txtSource.ClearUndo();
                txtSource.ClearStylesBuffer();
                txtSource.Text = reader.ReadToEnd();
                txtSource.SetVisibleState(0, FastColoredTextBoxNS.VisibleState.Visible);
                txtSource.Selection = txtSource.GetRange(0, 0);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
            finally
            {
                if (reader != null)
                    reader.Close();
            }
        }

        private void LoadGrammarFile(string path)
        {
            StreamReader reader = null;
            try
            {
                reader = new StreamReader(path);
                txtGrammarSource.Text = null;  //to clear any old formatting
                txtGrammarSource.ClearUndo();
                txtGrammarSource.ClearStylesBuffer();
                txtGrammarSource.Text = reader.ReadToEnd();
                txtGrammarSource.SetVisibleState(0, FastColoredTextBoxNS.VisibleState.Visible);
                txtGrammarSource.Selection = txtGrammarSource.GetRange(0, 0);
            }
            catch (Exception e)
            {
                MessageBox.Show(e.Message);
            }
            finally
            {
                if (reader != null)
                    reader.Close();
            }
        }

        //Source highlighting
        FastColoredTextBoxHighlighter _highlighter;
        FastColoredTextBoxHighlighter _anyTextHighlighter;
        private void StartHighlighter()
        {
            if (_highlighter != null)
                StopHighlighter();
            if (chkDisableHili.Checked) return;
            if (!_parser.Language.CanParse()) return;
            _highlighter = new FastColoredTextBoxHighlighter(txtSource, _language);
            _highlighter.Adapter.Activate();
        }
        private void StopHighlighter()
        {
            if (_highlighter == null) return;
            _highlighter.Dispose();
            _highlighter = null;
            ClearHighlighting();
        }
        private void StartAnyTextHighlighter()
        {
            if (_anyTextHighlighter != null)
                StopAnyTextHighlighter();
            if (chkDisableHili.Checked) return;
            if (!_anytextParser.Language.CanParse()) return;
            _anyTextHighlighter = new FastColoredTextBoxHighlighter(txtGrammarSource, _anytextData);
            _anyTextHighlighter.Adapter.Activate();
        }

        private void StopAnyTextHighlighter()
        {
            if (_anyTextHighlighter == null) return;
            _anyTextHighlighter.Dispose();
            _anyTextHighlighter = null;
            ClearAnytextHighlighting();
        }
        private void ClearHighlighting()
        {
            var selectedRange = txtSource.Selection;
            var visibleRange = txtSource.VisibleRange;
            var firstVisibleLine = Math.Min(visibleRange.Start.iLine, visibleRange.End.iLine);

            var txt = txtSource.Text;
            txtSource.Clear();
            txtSource.Text = txt; //remove all old highlighting

            txtSource.SetVisibleState(firstVisibleLine, FastColoredTextBoxNS.VisibleState.Visible);
            txtSource.Selection = selectedRange;
        }
        private void ClearAnytextHighlighting()
        {
            var selectedRange = txtGrammarSource.Selection;
            var visibleRange = txtGrammarSource.VisibleRange;
            var firstVisibleLine = Math.Min(visibleRange.Start.iLine, visibleRange.End.iLine);

            var txt = txtGrammarSource.Text;
            txtGrammarSource.Clear();
            txtGrammarSource.Text = txt; //remove all old highlighting

            txtGrammarSource.SetVisibleState(firstVisibleLine, FastColoredTextBoxNS.VisibleState.Visible);
            txtGrammarSource.Selection = selectedRange;
        }
        private void EnableHighlighter(bool enable)
        {
            if (_highlighter != null)
                StopHighlighter();
            if (enable)
                StartHighlighter();
        }

        private bool DoSearch(TextBoxBase textBox, string fragment, int start)
        {
            textBox.SelectionLength = 0;
            // Compile the regular expression.
            Regex r = new Regex(fragment, RegexOptions.IgnoreCase);
            // Match the regular expression pattern against a text string.
            Match m = r.Match(textBox.Text.Substring(start));
            if (m.Success)
            {
                int i = 0;
                Group g = m.Groups[i];
                CaptureCollection cc = g.Captures;
                Capture c = cc[0];
                textBox.SelectionStart = c.Index + start;
                textBox.SelectionLength = c.Length;
                textBox.Focus();
                textBox.ScrollToCaret();
                return true;
            }
            return false;
        }//method

        public TextBoxBase GetSearchContentBox()
        {
            switch (tabGrammar.SelectedIndex)
            {
                case 0:
                    return txtTerms;
                case 1:
                    return txtNonTerms;
                case 2:
                    return txtParserStates;
                //case 4:
                //  return txtSource;
                default:
                    return null;
            }//switch
        }

        #endregion

        #region Controls event handlers
        //Controls event handlers ###################################################################################################
        private void btnParse_Click(object sender, EventArgs e)
        {
            ParseSample();
        }

        private void btnRun_Click(object sender, EventArgs e)
        {
            RunSample();
        }

        private void tvParseTree_AfterSelect(object sender, TreeViewEventArgs e)
        {
            if (_treeClickDisabled)
                return;
            var vtreeNode = tvParseTree.SelectedNode;
            if (vtreeNode == null) return;
            var parseNode = vtreeNode.Tag as ParseTreeNode;
            if (parseNode == null) return;
            ShowSourcePosition(parseNode.Span.Location.Position, 1);
        }

        bool _changingGrammar;

        private void btnFileOpen_Click(object sender, EventArgs e)
        {
            if (dlgOpenFile.ShowDialog() != DialogResult.OK) return;
            ClearParserOutput();
            LoadSourceFile(dlgOpenFile.FileName);
        }

        private void txtSource_TextChanged(object sender, FastColoredTextBoxNS.TextChangedEventArgs e)
        {
            _parseTree = null; //force it to recompile on run
        }

        private void cboParseMethod_SelectedIndexChanged(object sender, EventArgs e)
        {
            //changing grammar causes setting of parse method combo, so to prevent double-call to ConstructParser
            // we don't do it here if _changingGrammar is set
            if (!_changingGrammar)
                CreateParser();
        }

        private void gridParserTrace_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (_context == null || e.RowIndex < 0 || e.RowIndex >= _context.ParserTrace.Count) return;
            var entry = _context.ParserTrace[e.RowIndex];
            switch (e.ColumnIndex)
            {
                case 0: //state
                case 3: //action
                    LocateParserState(entry.State);
                    break;
                case 1: //stack top
                    if (entry.StackTop != null)
                        ShowSourcePositionAndTraceToken(entry.StackTop.Span.Location.Position, entry.StackTop.Span.Length);
                    break;
                case 2: //input
                    if (entry.Input != null)
                        ShowSourcePositionAndTraceToken(entry.Input.Span.Location.Position, entry.Input.Span.Length);
                    break;
            }//switch
        }

        private void lstTokens_Click(object sender, EventArgs e)
        {
            if (lstTokens.SelectedIndex < 0)
                return;
            Token token = (Token)lstTokens.SelectedItem;
            ShowSourcePosition(token.Location.Position, token.Length);
        }

        private void gridCompileErrors_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex < 0 || e.RowIndex >= gridCompileErrors.Rows.Count) return;
            var err = gridCompileErrors.Rows[e.RowIndex].Cells[1].Value as LogMessage;
            switch (e.ColumnIndex)
            {
                case 0: //state
                case 1: //stack top
                    ShowSourcePosition(err.Location.Position, 1);
                    break;
                case 2: //input
                    if (err.ParserState != null)
                        LocateParserState(err.ParserState);
                    break;
            }//switch
        }

        private void gridGrammarErrors_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex < 0 || e.RowIndex >= gridGrammarErrors.Rows.Count) return;
            var state = gridGrammarErrors.Rows[e.RowIndex].Cells[2].Value as ParserState;
            if (state != null)
                LocateParserState(state);
        }

        private void lnkShowErrLocation_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
        }

        private void lnkShowErrStack_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
        {
        }

        private void btnLocate_Click(object sender, EventArgs e)
        {
            if (_parseTree == null)
                ParseSample();
            var p = txtSource.SelectionStart;
            tvParseTree.SelectedNode = null; //just in case we won't find
            SelectTreeNode(tvParseTree, LocateTreeNode(tvParseTree.Nodes, p, node => (node.Tag as ParseTreeNode).Span.Location.Position));
            txtSource.Focus(); //set focus back to source
        }

        private TreeNode LocateTreeNode(TreeNodeCollection nodes, int position, Func<TreeNode, int> positionFunction)
        {
            TreeNode current = null;
            //Find the last node in the list that is "before or at" the position
            foreach (TreeNode node in nodes)
            {
                if (positionFunction(node) > position) break; //from loop
                current = node;
            }
            //if current has children, search them
            if (current != null && current.Nodes.Count > 0)
                current = LocateTreeNode(current.Nodes, position, positionFunction) ?? current;
            return current;
        }

        private void chkDisableHili_CheckedChanged(object sender, EventArgs e)
        {
            if (!_loaded) return;
            EnableHighlighter(!chkDisableHili.Checked);
        }

        #endregion

        private void grammarAddButton_Click(object sender, EventArgs e)
        {
            LoadGrammar();
        }

        public void LoadGrammar()
        {
            ClearParserOutput();
            if (_anytextParser == null || !_anytextParser.Language.CanParse()) return;
            GC.Collect(); //to avoid disruption of perf times with occasional collections
            ParseTree parseTree;
            ParsingContext context;
            try
            {
                context = _anytextParser.Parse(txtGrammarSource.Text, "<source>");

                parseTree = context.CurrentParseTree;

                ShowCompilerErrors(parseTree);
                ShowCompileStats(parseTree);
            }
            catch (Exception ex)
            {
                gridCompileErrors.Rows.Add(null, ex.Message, null);
                tabBottom.SelectedTab = pageParserOutput;
                throw;
            }

            if (parseTree == null || parseTree.Status != ParseTreeStatus.Parsed) return;

            var grammar = _anytextTransformation.CreateGrammar(parseTree);
            var typeSystem = new AdaptiveTypeSystem();
            this._classBuilder.AugmentGrammar(grammar, typeSystem);


            try
            {
                ClearLanguageInfo();
                ClearParserOutput();
                ClearRuntimeInfo();

                _changingGrammar = true;
                _grammar = grammar;
                ShowLanguageInfo();
                CreateParser();
            }
            finally
            {
                _changingGrammar = false; //in case of exception
            }
        }

        private void grammarLoadButton_Click(object sender, EventArgs e)
        {
            if (dlgOpenFile.ShowDialog() != DialogResult.OK) return;
            ClearParserOutput();
            LoadGrammarFile(dlgOpenFile.FileName);
        }

        private void grammarSaveButton_Click(object sender, EventArgs e)
        {

        }

    }//class
}